/*
 * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package jsr203.sun.nio.fs;

import sun.misc.Cleaner;
import sun.misc.Unsafe;

/**
 * Factory for native buffers.
 */

class NativeBuffers {
    private NativeBuffers() { }

    private static final Unsafe unsafe = CopyMemory.getUnsafe();

    private static final int TEMP_BUF_POOL_SIZE = 3;
    private static ThreadLocal<NativeBuffer>[] buffers;

    @SuppressWarnings("unchecked")
    static ThreadLocal<NativeBuffer>[] createThreadLocalArray() {
        return new ThreadLocal[TEMP_BUF_POOL_SIZE];
    }

    static {
        buffers = createThreadLocalArray();
        for (int i=0; i<TEMP_BUF_POOL_SIZE; i++)
            buffers[i] = new ThreadLocal<NativeBuffer>();
    }

    private static class Deallocator implements Runnable {
        private final long address;

        Deallocator(long address) {
            this.address = address;
        }

        public void run() {
            unsafe.freeMemory(address);
        }
    }

    /**
     * Returns a native buffer of, at least, the given size. The buffer is
     * returned from the per-thread cache if available; otherwise a new buffer
     * is allocated.
     */
    static NativeBuffer getNativeBuffer(int size) {
        // Grab a buffer if available
        for (int i=0; i<TEMP_BUF_POOL_SIZE; i++) {
            NativeBuffer buffer = buffers[i].get();
            if (buffer != null && buffer.size() >= size) {
                buffers[i].set(null);
                return buffer;
            }
        }
        // Make a new one of at least 4k
        if (size < 4096) size = 4096;
        long address = unsafe.allocateMemory(size);
        NativeBuffer buffer = new NativeBuffer(address, size);
        Cleaner cl = Cleaner.create(buffer, new Deallocator(address));
        buffer.setCleaner(cl);
        return buffer;
    }

    /**
     * Releases the given buffer. If there is space in the per-thread cache
     * then the buffer goes into the cache; otherwise the memory is deallocated.
     */
    static void releaseNativeBuffer(NativeBuffer buffer) {
        if (buffer == null) return;

        // Put it in an empty slot if such exists
        for (int i=0; i<TEMP_BUF_POOL_SIZE; i++) {
            if (buffers[i].get() == null) {
                buffers[i].set(buffer);
                return;
            }
        }
        // Otherwise replace a smaller one in the cache if such exists
        for (int i=0; i<TEMP_BUF_POOL_SIZE; i++) {
            NativeBuffer inCache = buffers[i].get();
            if (inCache.size() < buffer.size()) {
                inCache.cleaner().clean();
                buffers[i].set(buffer);
                return;
            }
        }

        // free it
        buffer.cleaner().clean();
    }

    /**
     * Allocates a native buffer for the bytes of the given byte array.
     */
    static NativeBuffer asNativeBuffer(byte[] path) {
        int len = path.length;
        NativeBuffer buffer = getNativeBuffer(len+1);

        // FIXME - store reference to object in NativeBuffer to avoid copying
        // into the buffer for each I/O.

        // copy into buffer and zero terminate
        CopyMemory.fromByteArray(path, buffer.address(), len);
        // long offset = Unsafe.ARRAY_BYTE_BASE_OFFSET;
        // unsafe.copyMemory(path, offset, null, buffer.address(), (long)len);
        // unsafe.putByte(buffer.address() + len, (byte)0);
        return buffer;
    }
}
